#pragma once


#ifdef __linux__

#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>

#include <cstring>
#include <string>
#include <cerrno>
#include <functional>
#include <unordered_set>

#include "../service/rpc_service.h"
#include "../util/serial_util.h"


namespace lurpc {


/*************************
 * Socket Client
 * Linux platform
 ************************/
class socket_client {
public:
    static constexpr int BUF_SIZE = 2048;

public:
    static std::string send_msg(int, const std::string&, const std::string&);

public:
    socket_client();
    socket_client(const socket_client&) = delete;
    socket_client(socket_client&&) noexcept;

public:
    ~socket_client();

public:
    int connect_to_server(int, const std::string&);
    std::string exec_cmd(const std::string&);
    int close_conn();

    template<typename T, typename ... Args>
    T remote_call(uint32_t service_id, const Args& ... _args);
    
private:
    int _sock;
};

std::string socket_client::send_msg(int _port, const std::string& _host, const std::string& _msg) {
    int cli_sock = socket(AF_INET, SOCK_STREAM, 0);

    std::string ans;

    if (cli_sock < 0) {
        printf("socket error: %s\n", strerror(errno));
        return "";
    }

    sockaddr_in srv_addr {};
    memset(&srv_addr, 0, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(_port);
    srv_addr.sin_addr.s_addr = inet_addr(_host.c_str());

    if (connect(cli_sock, (sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        close(cli_sock);
        printf("connect error: %s\n", strerror(errno));
        return "";
    }

    char buf[BUF_SIZE];

    if (write(cli_sock, _msg.c_str(), _msg.size()) < 0) {
        close(cli_sock);
        printf("write error: %s\n", strerror(errno));
        return "";
    }

    auto r_len = read(cli_sock, buf, BUF_SIZE);
    if (r_len < 0) {
        close(cli_sock);
        printf("read error: %s\n", strerror(errno));
        return "";
    }

    close(cli_sock);

    ans.append(buf, r_len);

    return ans;

}

socket_client::socket_client() {
    _sock = socket(AF_INET, SOCK_STREAM, 0);
}

socket_client::socket_client(socket_client&& _oth_cli) noexcept {
    _sock = _oth_cli._sock;
    _oth_cli._sock = -1;
}

socket_client::~socket_client() {
    if (_sock >= 0) {
        close(_sock);
    }
}

int socket_client::connect_to_server(int _p, const std::string& _h) {
    if (_sock < 0) {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }

    sockaddr_in srv_addr {};
    memset(&srv_addr, 0, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(_p);
    srv_addr.sin_addr.s_addr = inet_addr(_h.c_str());

    if (connect(_sock, (sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        printf("connect error: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

std::string socket_client::exec_cmd(const std::string& _cmd) {
    char buf[BUF_SIZE];

    if (write(_sock, _cmd.c_str(), _cmd.size()) < 0) {
        printf("write error: %s\n", strerror(errno));
        return "";
    }

    auto r_len = read(_sock, buf, BUF_SIZE);
    if (r_len < 0) {
        printf("read error: %s\n", strerror(errno));
        return "";
    }

    return std::string(buf, r_len);
}

int socket_client::close_conn() {
    if (_sock >= 0) {
        return close(_sock);
    }
    return 0;
}

template<class T, class... Args>
T socket_client::remote_call(uint32_t service_id, const Args &..._args) {
    service_conf svc = rpc_services::get(service_id);

    // no such service configured in client
    if (svc._port == -1) {
        printf("No such service id in client: %d\n", service_id);
        return {};
    }

    std::string _args_buf = uint32_to_bytes(service_id);
    _args_buf.append(serialize(_args...));

    std::string resp = exec_cmd(_args_buf);

    // no such service registered in server
    if (resp == "[NON_SVC]") {
        printf("No such service id in server: %d\n", service_id);
        return {};
    }

    try {
        return deserialize<T>(resp);
    }
    catch (const std::exception& e) {
        printf("remote call falied: %s\n", e.what());
    }

    return {};
}


/*************************
 * Socket Server
 * Linux platform
 * Using epoll
 ************************/
class socket_server {
public:
    static constexpr int BACKLOG_SIZE = 32;
    static constexpr int KEVENTS_CNT = 64;
    static constexpr int EPEVENTS_CNT = 64;
    static constexpr int BUF_SIZE = 2048;

public:
    socket_server();
    socket_server(const socket_server&) = delete;
    socket_server(socket_server&&) noexcept;
    socket_server(int, std::string, std::function<std::string(const std::string&)>);

public:
    ~socket_server();

public:
    int start();

private:
    void __handle_close(int, int);

private:
    int _sock;
    int _port;
    std::string _host;
    std::unordered_set<int> _inv_fds;
    std::function<std::string(const std::string&)> _handle_message;

};

socket_server::socket_server() : _sock(-1), _port(4399), _host("127.0.0.1"), _handle_message() { }

socket_server::socket_server(socket_server&& _sock_srv) noexcept {
    _sock = _sock_srv._sock;
    _port = _sock_srv._port;
    _host = _sock_srv._host;
    _handle_message = std::move(_sock_srv._handle_message);
    _sock_srv._sock = -1;
    _sock_srv._port = -1;
    _sock_srv._host.clear();
    _sock_srv._handle_message = std::function<std::string(const std::string&)>();
}

socket_server::socket_server(int _p, std::string _h, std::function<std::string(const std::string&)> _message_handler)
        : _sock(-1), _port(_p), _host(std::move(_h)), _handle_message(std::move(_message_handler)) { }

socket_server::~socket_server() {
    if (_sock >= 0) close(_sock);
    for (auto& fd : _inv_fds) if (fd >= 0) close(fd);
}

int socket_server::start() {
    if (_port < 0) return -1;
    if (_host.empty()) return -1;

    if ((_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }

    fcntl(_sock, F_SETFL, fcntl(_sock, F_GETFL) | O_NONBLOCK);

    sockaddr_in srv_addr {
            .sin_family = AF_INET,
            .sin_port = htons(_port),
            .sin_addr {
                    .s_addr = inet_addr(_host.c_str())
            }
    };

    if (bind(_sock, (sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        printf("bind error: %s\n", strerror(errno));
        return -1;
    }

    if (listen(_sock, BACKLOG_SIZE) < 0) {
        printf("listen error: %s\n", strerror(errno));
        return -1;
    }

    int ep_fd = epoll_create(64);
    struct epoll_event srv_event {
            .events = EPOLLIN,
            .data {
                    .fd = _sock
            }
    };

    epoll_ctl(ep_fd, EPOLL_CTL_ADD, _sock, &srv_event);

    struct epoll_event ep_events[EPEVENTS_CNT];

    char buf[BUF_SIZE];
    struct epoll_event _r_event { };

    while (true) {
        int cnt = epoll_wait(ep_fd, ep_events, EPEVENTS_CNT, 500);
        if (cnt < 0) {
            printf("epoll event cnt < 0: %s\n", strerror(errno));
            break;
        }
        for (int i = 0; i < cnt; ++i) {
            int r_sock = ep_events[i].data.fd;
            if (r_sock == _sock) {
                struct sockaddr_in cli_addr { };
                socklen_t cli_addr_len = sizeof(cli_addr);
                int cli_sock = accept(_sock, (sockaddr *) &cli_addr, &cli_addr_len);
                if (cli_sock < 0) {
                    printf("accept error: %s\n", strerror(errno));
                    continue;
                }
                _inv_fds.insert(cli_sock);
                fcntl(cli_sock, F_SETFL, fcntl(cli_sock, F_GETFL) | O_NONBLOCK);
                _r_event.events = EPOLLIN | EPOLLET;
                _r_event.data.fd = cli_sock;
                epoll_ctl(ep_fd, EPOLL_CTL_ADD, cli_sock, &_r_event);
            }
            else {
                char* _r_ptr = buf;
                int r_len = recv(r_sock, buf, BUF_SIZE, 0);

                if (r_len <= 0) {
                    if (r_len < 0) printf("recv error: %s\n", strerror(errno));
                    __handle_close(r_sock, ep_fd);
                    continue;
                }
                else {
                    _r_ptr = buf + r_len;
                    while ((r_len = recv(r_sock, _r_ptr, BUF_SIZE, 0)) > 0) {
                        _r_ptr += r_len;
                    }
                }

                std::string _call_bkv = _handle_message(std::string(buf, _r_ptr - buf));

                if (send(r_sock, _call_bkv.c_str(), _call_bkv.size(), 0) < 0) {
                    printf("send error: %s\n", strerror(errno));
                    __handle_close(r_sock, ep_fd);
                    continue;
                }
            }
        }
    }

    return 0;
}

inline void socket_server::__handle_close(int _sk, int _ek) {
    close(_sk);
    _inv_fds.erase(_sk);
    epoll_ctl(_ek, EPOLL_CTL_DEL, _sk, nullptr);
}

}

#endif
